using System;
using System.Net;
using System.Net.Sockets;

namespace SharpMud.Server.Systems.Net
{
	/// <summary>
	/// Short Listen-On Url. Used to designate local-port/address and listening protocol couplets.
	/// </summary>
	/// <remarks>
	///	  -------------------------------------------------------------------------------
	///	    Input Format Specification:
	///		--------------------------
	///		SLU = connection_type_prefix  bind_address  port_suffix
	///		protocol_prefix = ( "telnet" | "http" | "irc" | "mudftp" etc ) "://" | ""
	///		bind_address = bind_ipv4_address | "[" bind_ipv6_address "]" | ""
	///		bind_ipv4_address = a valid ipv4 address | "0.0.0.0"
	///		bind_ipv6_address = a valid ipv6 address | ""
	///		port_suffix = ( ":" [ ( "+" | "-" ) ] followed by a number in the range 1024 -> 65535 ) | ""
	///	  -------------------------------------------------------------------------------
	///	e.g. "telnet://:4000"					- telnet on port 4000 for all ipv4
	///	e.g. "http://[feb0:0:0:ffff::42]:4001"  - http on port 4001 for the ipv6 address "feb0:0:0:ffff::42"
	///	e.g. "irc://192.168.0.1:4002"			- irc on port 4002 for the ipv4 address "192.168.0.1"
	///	e.g. "mudftp://[::1]:4003"				- mudftp on port 4003 for the local loopback ipv6 address "::1"
	/// 
	///	notes:
	///	- + or - in the port_suffix indicates an offset from the main port
	/// 
	///	- if no address is specified, but a port is the mud will listen on all 
	///	  ipv4 addresses for that given port
	///	  e.g. "http://:4080"
	/// .
	///	- if the address "0.0.0.0" is specified, the mud will listen on all available
	///	  ipv4 addresses for that given port.
	///	  e.g. "http://0.0.0.0:4080"
	/// 
	///	- if no address is specified but empty []'s are provided along with a port, 
	///	  the mud will listen on all ipv6 addresses for that given port.
	///	  e.g. "http://[]:4080"
	///	  Alternatively [::] can be used for all ipv6 addresses
	/// </remarks>
	public class SLU
	{
		#region Support Classes
		private class ProtocolDescriptor
		{
			private string _SluPrefix;
			private ProtocolDescriptor()
			{
			}
			private ProtocolDescriptor(string sluPrefix)
			{
				_SluPrefix = sluPrefix.ToLower().Trim();
			}
			public override string ToString()
			{
				return _SluPrefix;
			}
			public static ProtocolDescriptor Parse(string val)
			{
				return new ProtocolDescriptor(val.ToLower().ToString());
			}
			public static ProtocolDescriptor PullFromSLU(string slu)
			{
				int pds = 0;
				int pde = slu.IndexOf(':');

				if(pde==pds)
					throw new InvalidFormatException("Zero-length protocol descriptor not allowed.");
				if(pde<0)
					throw new InvalidFormatException("End of protocol descriptor ( '://' ) not found.");

				return Parse(slu.Substring(pds,pde-pds));
			}
		}

		private class AddressDescriptor
		{
			private System.Net.IPAddress _IPAddress;
			private AddressDescriptor()
			{
			}
			public AddressDescriptor(System.Net.IPAddress ipAddress)
			{
				if(
					(ipAddress.AddressFamily != System.Net.Sockets.AddressFamily.InterNetwork)
					&&
					(ipAddress.AddressFamily != System.Net.Sockets.AddressFamily.InterNetworkV6)
					)
				{
					throw new ArgumentException("An address descriptor can only represent an InterNetwork (IPv4) or InterNetworkV6 (IPv6) addresses.");
				}
				this._IPAddress = ipAddress;
			}
			/// <summary>
			/// Returns the string representation of this Address Descriptor
			/// </summary>
			public override string ToString()
			{
				if(this._IPAddress.AddressFamily == System.Net.Sockets.AddressFamily.InterNetwork)
				{
					if(this._IPAddress == System.Net.IPAddress.Any)
						return "";
					else
						return (this._IPAddress.ToString());
				}
				if(this._IPAddress.AddressFamily == System.Net.Sockets.AddressFamily.InterNetworkV6)
				{
					if(this._IPAddress == System.Net.IPAddress.IPv6Any)
						return "[]";
					else
						return "[" + this._IPAddress.ToString() + "]";
				}
				return (this._IPAddress.ToString());
			}
			/// <summary>
			/// Parses a string into an AddressDescriptor
			/// </summary>
			public static AddressDescriptor Parse(string val)
			{
				if(val.Length==0)
					return new AddressDescriptor(System.Net.IPAddress.Any);

				if(val.StartsWith("["))
				{
					if(val.EndsWith("]"))
					{
						if(val=="[]" || val=="[::]")
							return new AddressDescriptor(System.Net.IPAddress.IPv6Any);
						else
						{
							return new AddressDescriptor(System.Net.IPAddress.Parse(val.Substring(1,val.Length-2)));
						}
					}
					else
						throw new InvalidFormatException("End of IPv6 address descriptor ( ']' ) not found, (but beginning is present).");
				}
				else
				{
					if(val.EndsWith("]"))
					{
						throw new InvalidFormatException("Beginning of IPv6 address descriptor ( '[' ) not found, (but end is present).");
					}
					else
					{
						//IPv4 address
						return new AddressDescriptor(System.Net.IPAddress.Parse(val));
					}
				}
				throw new InvalidFormatException("The AddressDescriptor could not be parsed.");
			}
			public static AddressDescriptor PullFromSLU(string slu)
			{
				const string pd_ad_seperator = "://";
				if(slu.Substring(slu.IndexOf(pd_ad_seperator[0]),pd_ad_seperator.Length)!=pd_ad_seperator)
				{
					throw new InvalidFormatException("The seperator for between the ProtocolDescriptor and the AddressDescriptor was not found.");
				}
				int ads = slu.IndexOf(pd_ad_seperator,0)+pd_ad_seperator.Length;
				int ade = slu.IndexOf(':',ads);
				
				if(ade<0)
					throw new InvalidFormatException("The seperator for between the AddressDescriptor and the PortDescriptor was not found.");

				return Parse(slu.Substring(ads,ade-ads));
			}

			public System.Net.IPAddress IPAddress
			{
				get
				{
					return this._IPAddress;
				}
				set
				{
					if(
						(value.AddressFamily != System.Net.Sockets.AddressFamily.InterNetwork)
						||
						(value.AddressFamily != System.Net.Sockets.AddressFamily.InterNetworkV6)
						)
					{
						throw new ArgumentException("An AddressDescriptor can only represent an InterNetwork (IPv4) or InterNetworkV6 (IPv6) addresses.");
					}
					this._IPAddress = value;
				}
			}
		}

		private class PortDescriptor
		{
			private bool _IsRelative;
			private int  _NumericalValue;


			public bool IsRelative
			{
				get
				{
					return _IsRelative;
				}
			}

			public int NumericalValue
			{
				get
				{
					return _NumericalValue;
				}
			}

			public override string ToString()
			{
				if( (this._NumericalValue<0) || (this._IsRelative==false) )
					return _NumericalValue.ToString();
				else
					return "+" + _NumericalValue.ToString();

			}
			public static PortDescriptor Parse(string val)
			{
				PortDescriptor pd = new PortDescriptor();
				try
				{
					if(val.StartsWith("+"))
					{
						pd._IsRelative = true;
						pd._NumericalValue = int.Parse(val.Substring(1,val.Length-1));
					}
					else if(val.StartsWith("-"))
					{
						pd._IsRelative = true;
						pd._NumericalValue = int.Parse(val);
					}
					else
					{
						pd._IsRelative = false;
						pd._NumericalValue = int.Parse(val);
					}
				}
				catch
				{
					throw new InvalidFormatException("There was an error parsing the PortDescriptor.");
				}
				return pd;
			}
			public static PortDescriptor PullFromSLU(string slu)
			{
				try
				{
					int ads = slu.LastIndexOf(':') + 1;	
					return Parse(slu.Substring(ads,slu.Length - ads));
				}
				catch(InvalidFormatException e)
				{
					throw e;
				}
				catch(System.Exception e)
				{
					throw new InvalidFormatException();
				}
			}
		}

		public class InvalidFormatException : System.Exception
		{
			private const string baseMsg = "There is the error in the format of a Short-ListenOn-Url.";
			public InvalidFormatException()
				:base(baseMsg.Trim())
			{
			}
			public InvalidFormatException(string additionalInfo)
				:base(baseMsg.Trim() + " " + additionalInfo.Trim())
			{
			}
		}
		#endregion

		private ProtocolDescriptor	_protocol;
		private AddressDescriptor	_address;
		private PortDescriptor		_port;

		private SLU()
		{
		}

        /// <summary>
        /// 
        /// </summary>
        /// <param name="protocolDescriptor"></param>
        /// <param name="addressDescriptor"></param>
        /// <param name="portDescriptor"></param>
		public SLU(string protocolDescriptor,string addressDescriptor,string portDescriptor)
		{
			_protocol = ProtocolDescriptor.Parse(protocolDescriptor);
			_address = AddressDescriptor.Parse(addressDescriptor);
			_port = PortDescriptor.Parse(portDescriptor);
		}

        /// <summary>
        /// 
        /// </summary>
        /// <param name="protocolDescriptor"></param>
        /// <param name="address"></param>
        /// <param name="portDescriptor"></param>
		public SLU(string protocolDescriptor,System.Net.IPAddress address,string portDescriptor)
		{
			_protocol = ProtocolDescriptor.Parse(protocolDescriptor);
			_address = new AddressDescriptor(address);
			_port = PortDescriptor.Parse(portDescriptor);			
		}

        /// <summary>
        /// 
        /// </summary>
		public string				ProtocolDesignation
		{
			get
			{
				return this._protocol.ToString();
			}
			set
			{
				this._protocol = ProtocolDescriptor.Parse(value);
			}
		}

        /// <summary>
        /// 
        /// </summary>
		public System.Net.IPAddress	Address
		{
			get
			{
				return this._address.IPAddress;
			}
			set
			{
				this._address = new AddressDescriptor(value);
			}
		}

        /// <summary>
        /// 
        /// </summary>
		public string				PortDesignation
		{
			get
			{
				return this._port.ToString();
			}
			set
			{
				this._port = PortDescriptor.Parse(value);
			}
		}


		public int GetAbsolutePort(int basePort)
		{
			if(_port.IsRelative)
				return _port.NumericalValue + basePort;
			else
				return _port.NumericalValue;
		}

		public System.UriBuilder ToUriBuilder(int basePort)
		{
			return new System.UriBuilder(this.PortDesignation,this.Address.ToString(),this.GetAbsolutePort(basePort));
		}

		public override string ToString()
		{
			return _protocol.ToString() + "://" + _address.ToString() + ":" + _port.ToString();
		}
        
		public static SLU Parse(string val)
		{
			SLU slu = new SLU();
			slu._protocol = ProtocolDescriptor.PullFromSLU(val);
			slu._address = AddressDescriptor.PullFromSLU(val);
			slu._port = PortDescriptor.PullFromSLU(val);
			return slu;
		}
	}
}
